#include "w25qxx.h"
#include "spi.h"
#include "delay.h"


uint16_t W25QXX_TYPE=W25Q16;

//4K bytes=1 Sector
//16 Sector= 1 Block
//W25Q16
//W25Q16容量2M,=32 Block= 512 Sector 
													 
//在HAL_SPI1初始化基础上初始化SPI FLASH
void W25QXX_Init(void)
{ 
    uint8_t temp;
    HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_SET);//spi-flash初始化前先不片选
	MX_SPI1_Init();
	__HAL_SPI_ENABLE(&hspi1);//使能SPI1
  	SPI1_ReadWriteByte(0xFF);//启动传输预擦除
  	SPI1_SetSpeed(SPI_BAUDRATEPRESCALER_4);//临时调整波特率,APB2=84,SPI1=21
	W25QXX_TYPE=W25QXX_ReadID();
    if(W25QXX_TYPE==W25Q256)//仅对W25Q256有效
    {
        temp=W25QXX_ReadSR(3);              //判断字节地址模式
        if((temp&0X01)==0)			        //4字节地址模式
		{
			W25QXX_CS(0);//片选使能
			SPI1_ReadWriteByte(W25X_Enable4ByteAddr);//改4字节地址模式   
			W25QXX_CS(1);//取消片选 
		}
    }
}  

//读取状态寄存器
uint8_t W25QXX_ReadSR(uint8_t regno)   
{  
	uint8_t byte=0,command=0; 
    switch(regno)
    {
        case 1:
            command=W25X_ReadStatusReg1;    //状态寄存器1
            break;
        case 2:
            command=W25X_ReadStatusReg2;    //状态寄存器2
            break;
        case 3:
            command=W25X_ReadStatusReg3;    //状态寄存器3
            break;
        default:
            command=W25X_ReadStatusReg1;    
            break;
    }    
	W25QXX_CS(0);							//片选使能
	SPI1_ReadWriteByte(command);            //发送指仿   
	byte=SPI1_ReadWriteByte(0Xff);          //读取1字节
	W25QXX_CS(1);                           //取消片选    
	return byte;   
} 

//写状态寄存器
void W25QXX_Write_SR(uint8_t regno,uint8_t sr)   
{   
    uint8_t command=0;
    switch(regno)
    {
        case 1:
            command=W25X_WriteStatusReg1;    //状态寄存器1
            break;
        case 2:
            command=W25X_WriteStatusReg2;    //状态寄存器2
            break;
        case 3:
            command=W25X_WriteStatusReg3;    //状态寄存器3
            break;
        default:
            command=W25X_WriteStatusReg1;    
            break;
    }   
	W25QXX_CS(0);                           //片选使能   
	SPI1_ReadWriteByte(command);            //发送指仿     
	SPI1_ReadWriteByte(sr);                 //写1字节 
	W25QXX_CS(1);                           //取消片选     	      
}   

//W25QXX写使能
//WEL置位 
void W25QXX_Write_Enable(void)   
{
	W25QXX_CS(0);                           //片选使能 
    SPI1_ReadWriteByte(W25X_WriteEnable);   //发送指仿  
	W25QXX_CS(1);                           //取消片选      
} 

//W25QXX写禁止	
//WEL清零  
void W25QXX_Write_Disable(void)   
{  
	W25QXX_CS(0);                           //片选使能
    SPI1_ReadWriteByte(W25X_WriteDisable);  //发送指仿    
	W25QXX_CS(1);                           //取消片选
} 

//读取芯片ID
uint16_t W25QXX_ReadID(void)
{
	uint16_t Temp = 0;	  
	W25QXX_CS(0);				    
	SPI1_ReadWriteByte(W25X_ManufactDeviceID);//读取指令90h followed by 24bit address of 000000h	    
	SPI1_ReadWriteByte(0x00); 	    
	SPI1_ReadWriteByte(0x00); 	    
	SPI1_ReadWriteByte(0x00); 	 			   
	Temp|=SPI1_ReadWriteByte(0xFF)<<8;  
	Temp|=SPI1_ReadWriteByte(0xFF);	 
	W25QXX_CS(1);				    
	return Temp;
}   		    

//读取SPI FLASH  
//pBuffer:数据存储区
//ReadAddr:开始读取地址(24bit)
//NumByteToRead:要读取字节数(MAX=65535)
void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead)   
{ 
 	uint16_t i;   										    
	W25QXX_CS(0);                           
    SPI1_ReadWriteByte(W25X_ReadData);      
    if(W25QXX_TYPE==W25Q256)                		//Q256以上4字节地址要补高8位
    {
        SPI1_ReadWriteByte((uint8_t)((ReadAddr)>>24));    
    }
    SPI1_ReadWriteByte((uint8_t)((ReadAddr)>>16));  //3字节地址    
    SPI1_ReadWriteByte((uint8_t)((ReadAddr)>>8));   
    SPI1_ReadWriteByte((uint8_t)ReadAddr);   
    for(i=0;i<NumByteToRead;i++)
	{ 
        pBuffer[i]=SPI1_ReadWriteByte(0XFF);    	//循环读取 
    }
	W25QXX_CS(1);  				    	      
}  

//SPI在一顿(0~65535)写入少于256Byte数据
//pBuffer:数据存储区
//WriteAddr:开始写入地址(24bit)
//NumByteToWrite:要写入字节数(MAX=256)，不应超过本页剩余字节数??
void W25QXX_Write_Page(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
 	uint16_t i;  
    W25QXX_Write_Enable();                  //SET WEL 
	W25QXX_CS(0);                             
    SPI1_ReadWriteByte(W25X_PageProgram);    
    if(W25QXX_TYPE==W25Q256)                //Q256以上4字节地址要补高8位
    {
        SPI1_ReadWriteByte((uint8_t)((WriteAddr)>>24)); 
    }
    SPI1_ReadWriteByte((uint8_t)((WriteAddr)>>16)); //3字节地址     
    SPI1_ReadWriteByte((uint8_t)((WriteAddr)>>8));   
    SPI1_ReadWriteByte((uint8_t)WriteAddr);   
    for(i=0;i<NumByteToWrite;i++)SPI1_ReadWriteByte(pBuffer[i]);//循环写 
	W25QXX_CS(1);                          
	W25QXX_Wait_Busy();					   //等待写入结束
} 

//无检验写SPI FLASH 
//需确保所写地址范围内数据全部为0xFF，否则在非0xFF处将失败
//自动换页
//要确保地址不越界!
//pBuffer:数据存储区
//WriteAddr:开始写入地址(24bit)
//NumByteToWrite:要写入字节数(MAX=65536)
//CHECK OK
void W25QXX_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)   
{ 			 		 
	uint16_t pageremain;	   
	pageremain=256-WriteAddr%256; //单页剩余字节敿		 	    
	if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大于256byte
	while(1)
	{	   
		W25QXX_Write_Page(pBuffer,WriteAddr,pageremain);
		if(NumByteToWrite==pageremain)break;//写入结束
	 	else //NumByteToWrite>pageremain
		{
			pBuffer+=pageremain;
			WriteAddr+=pageremain;	

			NumByteToWrite-=pageremain;			  //减去写入字节敿
			if(NumByteToWrite>256)pageremain=256; //一次可写入256byte
			else pageremain=NumByteToWrite; 	  //不足256byte
		}
	};	    
} 

//写SPI FLASH  
//带擦除
//pBuffer:数据存储区
//WriteAddr:开始写入地址(24bit)					
//NumByteToWrite:要写入字节数(MAX=65536)   
uint8_t W25QXX_BUFFER[4096];		 
void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)   
{ 
	uint32_t secpos;
	uint16_t secoff;
	uint16_t secremain;	   
 	uint16_t i;    
	uint8_t * W25QXX_BUF;	  
   	W25QXX_BUF=W25QXX_BUFFER;	     
 	secpos=WriteAddr/4096;//扇区地址 
	secoff=WriteAddr%4096;//扇区内偏移
	secremain=4096-secoff;//扇区剩余空间大小   
 	//printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//测试
 	if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于4096byte
	while(1) 
	{	
		W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读取整个扇区内容
		for(i=0;i<secremain;i++)//校验数据
		{
			if(W25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除	  
		}
		if(i<secremain)//需要擦除
		{
			W25QXX_Erase_Sector(secpos);//擦除扇区
			for(i=0;i<secremain;i++)	//复制
			{
				W25QXX_BUF[i+secoff]=pBuffer[i];	  
			}
			W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写整个扇区内宿 

		}else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写已经擦除的，直接写入扇区剩余空闿 				   
		if(NumByteToWrite==secremain)break;//写入结束
		else//写入未结板
		{
			secpos++;//扇区地址+1
			secoff=0;//偏移位置0 	 

		   	pBuffer+=secremain;  //指针偏移
			WriteAddr+=secremain;//写地址偏移	   
		   	NumByteToWrite-=secremain;				//字节数递减
			if(NumByteToWrite>4096)secremain=4096;	//下一扇区还是写不宿
			else secremain=NumByteToWrite;			//下一扇区可以写完亿
		}	 
	};	 
}

//擦除整个芯片	  
//等待时间超长...
void W25QXX_Erase_Chip(void)   
{                                   
    W25QXX_Write_Enable();                  //SET WEL 
    W25QXX_Wait_Busy();   
  	W25QXX_CS(0);                          //片选使能 
    SPI1_ReadWriteByte(W25X_ChipErase);    //发送指仿  
	W25QXX_CS(1);                          //取消片选  	      
	W25QXX_Wait_Busy();   				   //等待擦除结束
}   

//擦除扇区
//Dst_Addr:扇区地址 根据实际容量设置
//擦除扇区最少时闿:150ms
void W25QXX_Erase_Sector(uint32_t Dst_Addr)   
{  
	//监视擦除情况，测试用   
 	//printf("fe:%x\r\n",Dst_Addr);	  
 	Dst_Addr*=4096;
    W25QXX_Write_Enable();                  //SET WEL 	 
    W25QXX_Wait_Busy();   
  	W25QXX_CS(0);                              
    SPI1_ReadWriteByte(W25X_SectorErase);   
    if(W25QXX_TYPE==W25Q256)                //Q256以上4字节地址要补高8位
    {
        SPI1_ReadWriteByte((uint8_t)((Dst_Addr)>>24)); 
    }
    SPI1_ReadWriteByte((uint8_t)((Dst_Addr)>>16));  //3字节地址   
    SPI1_ReadWriteByte((uint8_t)((Dst_Addr)>>8));   
    SPI1_ReadWriteByte((uint8_t)Dst_Addr);  
	W25QXX_CS(1);                            //取消片选   	      
    W25QXX_Wait_Busy();   				    //等待擦除完成
}  

//等待空闲
void W25QXX_Wait_Busy(void)   
{   
	while((W25QXX_ReadSR(1)&0x01)==0x01);   //等待BUSY清空
}  

//进入掉电模式
void W25QXX_PowerDown(void)   
{ 
  	W25QXX_CS(0);                           //片选使能
    SPI1_ReadWriteByte(W25X_PowerDown);     //发送指仿
	W25QXX_CS(1);                           //取消片选   	      
    delay_us(3);                            //等待TPD 
}   

//唤醒
void W25QXX_WAKEUP(void)   
{  
  	W25QXX_CS(0);                               //片选使能  
    SPI1_ReadWriteByte(W25X_ReleasePowerDown);  //发送指仿    
	W25QXX_CS(1);                               //取消片选   	      
    delay_us(3);                                //等待TRES1 
}   
